from fastapi import Request, status
from fastapi.encoders import jsonable_encoder
from starlette.middleware.base import BaseHTTPMiddleware, RequestResponseEndpoint
from starlette.responses import JSONResponse

from app.constants import AUTHORIZATION
from app.core.config import default_router_white_list, settings
from app.core.log import logger
from app.enums.error import ErrorCodeEnum
from app.exception import TokenException
from app.schemas.base_schema import ApiResult
from app.utils import context
from app.utils.router import matcher
from app.utils.token import check_login

# 白名单路由
router_white_list = default_router_white_list + settings.sa_token_white_list


class AuthMiddleware(BaseHTTPMiddleware):
    """
    登录认证 中间件

    该中间件用于拦截所有请求，验证用户是否已登录。
    如果请求路径在白名单中，则跳过认证；否则检查请求头中的token并进行登录校验。
    """

    async def dispatch(self, request: Request, call_next: RequestResponseEndpoint):
        """
        中间件主入口函数，处理每个 HTTP 请求的认证逻辑

        :param request: FastAPI 的 Request 对象，包含客户端请求信息
        :param call_next: 下一个中间件或路由处理函数
        :return: 返回处理后的响应结果
        """
        logger.debug("----------> 3. 登录认证 中间件")

        # 判断当前请求路径是否在白名单中
        if matcher.batch_match(request.url.path, router_white_list):
            # 白名单路由，无需认证，直接放行
            response = await call_next(request)
            logger.debug("<---------- 3. 登录认证 中间件")
            return response

        try:
            # 1.从请求头中获取token值
            token = request.headers.get(AUTHORIZATION)
            if not token:
                return self.fail(ErrorCodeEnum.UNAUTHORIZED.code, "未能读取到有效 token")

            # 2.校验token
            user_id = await check_login(token)
            context.set_token(token)
            context.set_user_id(user_id)

            # 3.继续执行下一个中间件
            response = await call_next(request)

            logger.debug("<---------- 3. 登录认证 中间件")
            return response
        except TokenException as e:
            logger.exception("登录认证抛出Token校验异常", e)
            return self.fail(e.code, e.message)
        except Exception as e:
            # 其它异常继续往上抛
            raise e
            # logger.exception("登录认证中间件抛出未知异常", e)
            # return self.fail(code=500, message=str(e), status_code=status.HTTP_400_BAD_REQUEST)

    @staticmethod
    def fail(code, message, status_code=status.HTTP_401_UNAUTHORIZED) -> JSONResponse:
        """ 构造失败响应结果 """
        return JSONResponse(
            status_code=status_code,
            content=jsonable_encoder(
                ApiResult.result(code=code, message=message)
            )
        )
